package com.wei.czz.common.po;

import com.wei.czz.common.tool.BaseEntity;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;

import java.util.Deque;
import java.util.LinkedList;

/**
 * Created by IntelliJ IDEA.
 *
 * @author wei
 * date: 2023-02-18 22:45:58
 * className: LimitConfigPo 限流配置数据封装类
 * version: 1.0
 * description:
 *  此类仅供内部使用
 */
@Getter
@Setter
@Accessors(chain = true)
public class LimitConfigPo extends BaseEntity {

    /**
     * 最大请求次数
     */
    private int maxNum;
    /**
     * 滑动窗口数量
     */
    private long windowNum;
    /**
     * 窗口间隔（单位：毫秒）
     */
    private long windowSpace;
    /**
     * 第一个窗口对应的时间戳
     */
    private long windowTime;
    /**
     * 限流时长（单位：毫秒）
     */
    private Integer limitTime;
    /**
     * 限流时回复消息
     */
    private String message;
    /**
     * 当前请求总数
     */
    private int totalCount;
    /**
     * 请求计数滑动窗口
     * 队列尾部节点为最新统计的节点
     * 尾节点之前的所有节点为了历史统计节点
     */
    private Deque<Integer> slideWindow;

    /**
     * 请求统计操作
     * @param now 当前系统时间戳
     * @return true-校验通过 false-校验拒绝
     */
    public boolean math(long now) {
        // 相减得到此次计算结果，是否落在已有窗口模块上
        long sub = now - windowTime;
        if (sub > windowSpace) {
            // 到了新的时间窗口模块，计算得到需要新增几个模块
            double num = Math.ceil((double) sub / windowSpace);
            if (num > windowNum) {
                // 需要新增的数量大于整个窗口的数量，直接重置整个滑动窗口
                slideWindow = new LinkedList<>();
                for (int i = 0; i < windowNum; i++) {
                    slideWindow.add(0);
                }
                // 重置请求总次数
                totalCount = 0;
            } else {

                for (int i = 0; i < num; i++) {
                    // 去除历史窗口模块
                    Integer count = slideWindow.pollFirst();
                    if (count != null && count != 0) {
                        totalCount -= count;
                    }
                    // 添加新的窗口模块
                    slideWindow.add(0);
                }
            }
            // 计算得到最新的窗口时间戳
            windowTime += (long) num * windowSpace;
        }
        if (totalCount >= maxNum) {
            // 超过最大请求次数，触发限流
            return false;
        }
        // 请求总数自增
        totalCount += 1;
        // 窗口计数自增
        Integer count = slideWindow.pollLast();
        count += 1;
        slideWindow.add(count);

        return true;
    }
}

